package com.chaoyer.cynet.common.utils;

import com.chaoyer.cynet.common.constant.HttpConnectionConstants;
import com.chaoyer.cynet.common.domain.HttpResult;
import com.chaoyer.cynet.common.domain.Result;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.AbstractHttpMessage;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Function;

public class HttpConnectionUtil {

    private static final Logger log = LoggerFactory.getLogger(HttpConnectionUtil.class);

    public static Long logValve = 1000L;

    public static Map<String, Object> commonHeader = null;

    public static Map<String, Object> getCommonHeader() {
        return commonHeader;
    }

    public static void setCommonHeader(Map<String, Object> commonHeader) {
        HttpConnectionUtil.commonHeader = commonHeader;
    }

    public static HttpClient getHttpClient() throws Exception {
        SSLContext ctx = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        }).build();
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(HttpConnectionConstants.SOCKET_TIMEOUT)
                .setConnectTimeout(HttpConnectionConstants.SOCKET_TIMEOUT)
                .setSocketTimeout(HttpConnectionConstants.SOCKET_TIMEOUT).build();
        CloseableHttpClient client = HttpClientBuilder.create().setSSLContext(ctx).setDefaultRequestConfig(config)
                .build();
        return client;
    }

    public static HttpClient getSoapHttpClient() throws Exception {
        SSLContext ctx = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        }).build();
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(HttpConnectionConstants.CONNECT_TIMEOUT)
                .setConnectTimeout(HttpConnectionConstants.CONNECT_TIMEOUT)
                .setSocketTimeout(HttpConnectionConstants.SOAP_SOCKET_TIMEOUT).build();
        CloseableHttpClient client = HttpClientBuilder.create().setSSLContext(ctx).setDefaultRequestConfig(config)
                .build();
        return client;
    }

    /*********************************** get **************************************/

    public static HttpResult get(String url) {
        return get(url, null, null);
    }

    public static HttpResult get(String url, Map<String, Object> params, Map<String, Object> headParams) {
        return get(url, params, headParams, HttpConnectionConstants.DEFAULT_ENCODE);
    }

    public static HttpResult get(String url, Map<String, Object> params, Map<String, Object> headParams,
                                 String enCode) {
        HttpResult result = null;
        try {
            result = get(getHttpClient(), url, params, headParams, enCode);
        } catch (Exception e) {
            log.error("get exception:" + url, e);
        }
        return result;
    }

    public static <T> T httpGet(String url, Class<T> clazz) {
        return httpGet(url, null, null, clazz);
    }

    public static <T> T httpGet(String url, Map<String, Object> params, Class<T> clazz) {
        return httpGet(url, params, null, HttpConnectionConstants.DEFAULT_ENCODE, clazz);
    }

    public static <T> T httpGet(String url, Map<String, Object> params, Map<String, Object> headParams,
                                Class<T> clazz) {
        return httpGet(url, params, headParams, HttpConnectionConstants.DEFAULT_ENCODE, clazz);
    }

    public static <T> T httpGet(String url, Map<String, Object> params, Map<String, Object> headParams, String enCode,
                                Class<T> clazz) {
        return httpGet(url, params, headParams, enCode, value -> value.getResult(clazz));
    }


    public static <T> T httpGet(String url, Type type) {
        return httpGet(url, null, null, type);
    }

    public static <T> T httpGet(String url, Map<String, Object> params, Type type) {
        return httpGet(url, params, null, HttpConnectionConstants.DEFAULT_ENCODE, type);
    }

    public static <T> T httpGet(String url, Map<String, Object> params, Map<String, Object> headParams, Type type) {
        return httpGet(url, params, headParams, HttpConnectionConstants.DEFAULT_ENCODE, type);
    }

    public static <T> T httpGet(String url, Map<String, Object> params, Map<String, Object> headParams, String enCode,
                                Type type) {
        return httpGet(url, params, headParams, enCode, value -> value.getResult(type));
    }

    private static <T> T httpGet(String url, Map<String, Object> params, Map<String, Object> headParams, String enCode,
                                 Function<HttpResult, T> function) {
        HttpResult result = get(url, params, headParams, enCode);
        if (result != null && result.success()) {
            return function.apply(result);
        } else {
            if (result == null) {
                log.error("httpGet {} result is null", url);
            } else {
                log.error("httpGet {} error:" + result.getCode() + "," + result.getContent(), url);
            }
        }
        return null;
    }

    /***********************************
     * post
     **************************************/

    public static HttpResult post(String url) {
        return post(url, null, null);
    }

    public static HttpResult post(String url, Map<String, Object> params, Map<String, Object> headParams) {
        return post(url, params, headParams, HttpConnectionConstants.DEFAULT_ENCODE);
    }

    public static HttpResult post(String url, Map<String, Object> params, Map<String, Object> headParams,
                                  String enCode) {
        HttpResult result = null;
        try {
            result = post(getHttpClient(), url, params, headParams, enCode);
        } catch (Exception e) {
            log.error("post exception:" + url, e);
        }
        return result;
    }

    public static <T> T httpPost(String url, Class<T> clazz) {
        return httpPost(url, null, null, clazz);
    }

    public static <T> T httpPost(String url, Map<String, Object> params, Class<T> clazz) {
        return httpPost(url, params, null, HttpConnectionConstants.DEFAULT_ENCODE, clazz);
    }

    public static <T> T httpPost(String url, Map<String, Object> params, Map<String, Object> headParams,
                                 Class<T> clazz) {
        return httpPost(url, params, headParams, HttpConnectionConstants.DEFAULT_ENCODE, clazz);
    }

    public static <T> T httpPost(String url, Map<String, Object> params, Map<String, Object> headParams, String enCode,
                                 Class<T> clazz) {
        HttpResult result = post(url, params, headParams, enCode);
        return handlerPostResult(url, result, value -> value.getResult(clazz));
    }

    public static <T> T httpPost(Map<String, Object> headParams, String url, String content, Class<T> clazz) {
        HttpResult result = post(headParams, url, content);
        return handlerPostResult(url, result, value -> value.getResult(clazz));
    }


    public static <T> T httpPost(String url, Type type) {
        return httpPost(url, null, null, type);
    }

    public static <T> T httpPost(String url, Map<String, Object> params, Type type) {
        return httpPost(url, params, null, HttpConnectionConstants.DEFAULT_ENCODE, type);
    }

    public static <T> T httpPost(String url, Map<String, Object> params, Map<String, Object> headParams, Type type) {
        return httpPost(url, params, headParams, HttpConnectionConstants.DEFAULT_ENCODE, type);
    }

    public static <T> T httpPost(String url, Map<String, Object> params, Map<String, Object> headParams, String enCode,
                                 Type type) {
        HttpResult result = post(url, params, headParams, enCode);
        return handlerPostResult(url, result, value -> value.getResult(type));
    }

    public static <T> T httpPost(Map<String, Object> headParams, String url, String content, Type type) {
        HttpResult result = post(headParams, url, content);
        return handlerPostResult(url, result, value -> value.getResult(type));
    }

    private static <T> T handlerPostResult(String url, HttpResult result, Function<HttpResult, T> function) {
        if (result != null && result.success()) {
            return function.apply(result);
        } else {
            if (result == null) {
                log.error("httpPost {} result is null", url);
            } else {
                log.error("httpPost {} error:" + result.getCode() + "," + result.getContent(), url);
            }
        }
        return null;
    }

    public static void putHead(AbstractHttpMessage httpMessage, Map<String, Object> headParams) {
        if (headParams != null && httpMessage != null) {
            for (Iterator<Entry<String, Object>> iterator = headParams.entrySet().iterator(); iterator.hasNext(); ) {
                Map.Entry<String, Object> entry = (Entry<String, Object>) iterator.next();
                String key = entry.getKey();
                Object val = entry.getValue();
                if (val instanceof String) {
                    httpMessage.addHeader(key, (String) val);
                } else {
                    httpMessage.addHeader(key, String.valueOf(val));
                }
            }
        }
    }

    /***********************************
     * base
     **************************************/

    public static HttpResult get(HttpClient httpClient, String url, Map<String, Object> params,
                                 Map<String, Object> headParams, String enCode) {

        HttpResult result = new HttpResult();
        HttpGet httpGet = null;
        try {
            long startTime = System.currentTimeMillis();
            if (!VerifyUtils.isEmpty(params)) {
                StringBuffer urls = new StringBuffer(url);
                String u = url;
                urls.append("?");
                for (Iterator<Entry<String, Object>> iterator = params.entrySet().iterator(); iterator.hasNext(); ) {
                    Map.Entry<String, Object> entry = (Entry<String, Object>) iterator.next();
                    Result<Void> resultForEach = Result.success();
                    String key = entry.getKey();
                    Object val = entry.getValue();
                    if (!handler(val, (value) -> {
                        try {
                            urls.append(key).append("=").append(URLEncoder.encode(String.valueOf(value), enCode))
                                    .append("&");
                        } catch (UnsupportedEncodingException e) {
                            log.error("get exception:" + u);
                            resultForEach.setThrowable(e);
                            resultForEach.setSuccess(false);
                        }
                    })) {
                        urls.append(key).append("=").append(URLEncoder.encode(String.valueOf(val), enCode)).append("&");
                    } else if (!resultForEach.isSuccess()) {
                        throw (UnsupportedEncodingException) resultForEach.getThrowable();
                    }
                }
                url = urls.toString().substring(0, urls.lastIndexOf("&"));
            }

            httpGet = new HttpGet(url);

            putHead(httpGet, headParams);
            putHead(httpGet, commonHeader);

            HttpResponse response = httpClient.execute(httpGet);
            long endTime = System.currentTimeMillis();
            result.setCode(response.getStatusLine().getStatusCode());
            HttpEntity entity = response.getEntity();
            result.setContent(EntityUtils.toString(entity));
            result.setCostTime(endTime - startTime);

        } catch (Exception e) {
            log.error("get exception:" + url, e);
        } finally {
            requestClose(httpClient, httpGet);
            if (result.getCostTime() > getLogValve()) {
                log.error("slow post requst:" + url + "," + result.getCostTime());
            }

        }

        return result;

    }

    public static HttpResult post(HttpClient httpClient, String url, Map<String, Object> params,
                                  Map<String, Object> headParams, String enCode) {
        HttpResult result = new HttpResult();
        HttpPost httpPost = null;
        try {
            long startTime = System.currentTimeMillis();
            httpPost = new HttpPost(url);

            putHead(httpPost, headParams);
            putHead(httpPost, commonHeader);

            if (!VerifyUtils.isEmpty(params)) {
                List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
                for (Iterator<Entry<String, Object>> iterator = params.entrySet().iterator(); iterator.hasNext(); ) {
                    Map.Entry<String, Object> entry = (Entry<String, Object>) iterator.next();
                    String key = entry.getKey();
                    Object val = entry.getValue();
                    if (!handler(val,
                            (value) -> nameValuePairs.add(new BasicNameValuePair(key, String.valueOf(value))))) {
                        nameValuePairs.add(new BasicNameValuePair(key, String.valueOf(val)));
                    }
                }
                httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, enCode));
            }
            HttpResponse response = httpClient.execute(httpPost);
            long endTime = System.currentTimeMillis();
            HttpEntity entity = response.getEntity();
            result.setCostTime(endTime - startTime);
            result.setCode(response.getStatusLine().getStatusCode());
            result.setContent(EntityUtils.toString(entity));
        } catch (Exception e) {
            log.error("post exception:" + url, e);
        } finally {
            requestClose(httpClient, httpPost);
            if (result.getCostTime() > getLogValve()) {
                log.error("slow post requst:" + url + "," + result.getCostTime());
            }
        }
        return result;
    }

    public static HttpResult post(Map<String, Object> headParams, String url, String content) {
        HttpResult result = null;
        try {
            result = post(getHttpClient(), url, content, headParams, HttpConnectionConstants.DEFAULT_ENCODE);
        } catch (Exception e) {
            log.error("post exception:" + url, e);
        }
        return result;
    }

    public static HttpResult post(HttpClient httpClient, String url, String content, Map<String, Object> headParams,
                                  String enCode) {
        HttpResult result = new HttpResult();
        HttpPost httpPost = null;
        try {
            long startTime = System.currentTimeMillis();
            httpPost = new HttpPost(url);
            Header head = new BasicHeader(HTTP.CONTENT_TYPE,
                    ContentType.create(HttpConnectionConstants.APPLICATION_JSON, enCode).toString());
            httpPost.addHeader(head);

            putHead(httpPost, headParams);
            putHead(httpPost, commonHeader);

            if (!VerifyUtils.isEmpty(content)) {
                // String encoderJson = URLEncoder.encode(content, enCode);
                StringEntity se = new StringEntity(content,
                        ContentType.create(HttpConnectionConstants.CONTENT_TYPE_TEXT_JSON, enCode));
                se.setContentEncoding(head);
                httpPost.setEntity(se);
            }

            HttpResponse response = httpClient.execute(httpPost);
            long endTime = System.currentTimeMillis();
            HttpEntity entity = response.getEntity();
            result.setCostTime(endTime - startTime);
            result.setCode(response.getStatusLine().getStatusCode());
            result.setContent(EntityUtils.toString(entity));
        } catch (Exception e) {
            log.error("post exception:" + url, e);
        } finally {
            requestClose(httpClient, httpPost);
            if (result.getCostTime() > getLogValve()) {
                log.error("slow post requst:" + url + "," + result.getCostTime());
            }
        }
        return result;
    }

    public static <T> T httpPostSoap11(Map<String, Object> headParams, String url, String content, Class<T> clazz,
                                       String function) {
        HttpResult result = postSoap11(headParams, url, content, function);
        return handlerPostResult(url, result, value -> value.getResult(clazz));
    }

    public static HttpResult postSoap11(Map<String, Object> headParams, String url, String content, String function) {
        HttpResult result = null;
        try {
            Header head = new BasicHeader(HTTP.CONTENT_TYPE,
                    ContentType
                            .create(HttpConnectionConstants.CONTENT_TYPE_TEXT_XML, HttpConnectionConstants.DEFAULT_ENCODE)
                            .toString());
            result = postSoap(getSoapHttpClient(), url, content, headParams, HttpConnectionConstants.DEFAULT_ENCODE, head,
                    function);
        } catch (Exception e) {
            log.error("post exception:" + url, e);
        }
        return result;
    }

    public static <T> T httpPostSoap12(Map<String, Object> headParams, String url, String content, Class<T> clazz,
                                       String function) {
        HttpResult result = postSoap12(headParams, url, content, function);
        return handlerPostResult(url, result, value -> value.getResult(clazz));
    }

    public static HttpResult postSoap12(Map<String, Object> headParams, String url, String content, String function) {
        HttpResult result = null;
        try {
            Header head = new BasicHeader(HTTP.CONTENT_TYPE, ContentType
                    .create(HttpConnectionConstants.APPLICATION_SOAP, HttpConnectionConstants.DEFAULT_ENCODE).toString());
            result = postSoap(getSoapHttpClient(), url, content, headParams, HttpConnectionConstants.DEFAULT_ENCODE, head,
                    function);
        } catch (Exception e) {
            log.error("post exception:" + url, e);
        }
        return result;
    }

    public static HttpResult postSoap(HttpClient httpClient, String url, String content, Map<String, Object> headParams,
                                      String enCode, Header head, String function) {
        HttpResult result = new HttpResult();
        HttpPost httpPost = null;
        try {
            long startTime = System.currentTimeMillis();
            httpPost = new HttpPost(url);
            httpPost.addHeader(head);

            putHead(httpPost, headParams);

            if (!VerifyUtils.isEmpty(content)) {
                StringEntity se = new StringEntity(content, Charset.forName(HttpConnectionConstants.DEFAULT_ENCODE));
                httpPost.setEntity(se);
            }

            HttpResponse response = httpClient.execute(httpPost);
            long endTime = System.currentTimeMillis();
            HttpEntity entity = response.getEntity();
            result.setCostTime(endTime - startTime);
            result.setCode(response.getStatusLine().getStatusCode());
            result.setContent(EntityUtils.toString(entity));
        } catch (Exception e) {
            log.error("post exception:" + url + ", function = " + function, e);
        } finally {
            requestClose(httpClient, httpPost);
            if (result.getCostTime() > getLogValve()) {
                log.error("slow post requst:" + url + ", function = " + function + "," + result.getCostTime());
            }
        }
        return result;
    }

    private static void requestClose(HttpClient httpClient, HttpRequestBase httpRequestBase) {
        if (httpRequestBase != null) {
            httpRequestBase.abort();
            try {
                ((CloseableHttpClient) httpClient).close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public static boolean handler(Object value, Consumer consumer) {
        if (value != null) {
            if (value.getClass().isArray()) {
                for (int i = 0, size = Array.getLength(value); i < size; i++) {
                    consumer.accept(Array.get(value, i));
                }
                return true;
            } else if (value instanceof Iterable) {
                ((Iterable) value).forEach(consumer);
                return true;
            }
        }
        return false;
    }

    public static Long getLogValve() {
        return logValve;
    }

    public static void setLogValve(Long logValve) {
        HttpConnectionUtil.logValve = logValve;
    }

    public static void main(String[] args) {
        HttpResult result = HttpConnectionUtil.get("https://github.com/Arronlong/httpclientUtil");
        System.out.println(result.getContent());
    }

}
